
/* Thinker.m, the brains in the color server app */

#import "Thinker.h"
#import "SpotView.h"
#import "ClientRecord.h"
#import "FullCopyList.h"
#import <libc.h>
#import <appkit/Panel.h>
#import <machkit/NXNetNameServer.h>
#import <machkit/senderIsInvalid.h>
#import <mach/mach.h>
#import <mach/mach_error.h>
#import <objc/hashtable.h>
#import <servers/netname.h>

#define REGISTERED_NAME "samsColorServer"
#define streq !strcmp

@implementation Thinker

//////////////////////////////////////////////////////
//		Shared server and client code
//////////////////////////////////////////////////////
- appDidInit:sender
{
	float r,g,b;
	NXColor theColor;

	spotList = [[FullCopyList alloc] init];

	// return a proxy for the server
	// this looks for a color server on any host.  In the real world,
	// you would probably specify a host.
	// this returns a local proxy to the remote "root" object

	server = [NXConnection connectToName:REGISTERED_NAME onHost:"*"];

	if (server)
	{
		// Become a client, already a server running
		isServer = NO;
		myConnection = [server connectionForProxy];
		[myConnection registerForInvalidationNotification:self];
		[server setProtocolForProxy:@protocol(spotServerMethods)];
		[win setTitle:"Spot Client"];
	}

	else

	{
		// Become the spot server, since no server is running
		// There is a window of vulnerability here; if 2 apps
		// launch nearly simultaneously, both will not find servers,
		// one will become the real server but the other will also
		// think it's the server (but will never get clients).
		// I should deal with this...

		isServer = YES;

		myConnection = [NXConnection registerRoot:self withName:REGISTERED_NAME];

		// printf("server: root connection is 0x%x\n", myConnection);

		[win setTitle:"Spot Server"];
		clientList = [[List alloc] init];

		server = self;	//so this can act as its own pseudo client...
	}

	[myConnection runFromAppKit];

	[mySpotView lateInit];

	[server addClient:self r:&r g:&g b:&b];
	theColor = NXConvertRGBToColor(r,g,b);
	[[win setBackgroundColor:theColor] display];

	return self;
}

- appWillTerminate:sender
{
	if (isServer)
	{
		int	i, n = [clientList count];

		for (i = 0; i < n; i++)
		{
			id theClient = [[clientList objectAt:i] client];
			if (theClient != self) [theClient serverTerminated];
		}
	}
	else
	{
		[server clientTerminated:self];
	}
	return self;
}

- server
{	return server;
}

- (BOOL)isServer
{	return isServer;
}

// this is sent by a connection
- senderIsInvalid:sender
{
	if (isServer)
	{
		int	i, n = [clientList count];

		for (i = 0; i < n; i++)
		{
			id theClientRecord = [clientList objectAt:i];
			if ([theClientRecord connection] == sender)
			{
				[self nukeClient: theClientRecord];
				break;
			}
		}
	}
	else
	{
		[self serverTerminated];
	}

	// now free the connection since it's no longer useful
	// this also frees the proxies that the connection maintained
	[sender free];

	return nil;
}

//////////////////////////////////////////////////////
//		Server code
//////////////////////////////////////////////////////

// the server needs to keep a local database of clients so it can
// message them all and associate port deaths to dead clients

- (void) addClient:remoteClient
	r:(out float *)r
	g:(out float *)g
	b:(out float *)b
{
	id connToClient, aSpot, client;

	if (remoteClient != self)
	{
		[remoteClient setProtocolForProxy:@protocol(spotClientMethods)];

		connToClient = [remoteClient connectionForProxy];
		[connToClient registerForInvalidationNotification:self];
	}
	else connToClient = nil;

	aSpot = [[[Spot alloc] init] addReference];
	client = [[ClientRecord alloc] 
		initClient:remoteClient connection:connToClient spot:aSpot];



	// the client list is used internally by the server, while the
	// spot list contains some duplicate information, but must
	// be exported.

	[clientList addObject:client];
	[spotList addObject:aSpot];

	NXConvertColorToRGB([aSpot color], r, g, b);

	[self sendSpotListToClients];

	return;
}

// only called in server by server
- nukeClient: theClientRecord
{
	id theSpot = [theClientRecord spot];
	[clientList removeObject:theClientRecord];
	[spotList removeObject:theSpot];
	[theSpot invalidate];
	[NXApp delayedFree:theSpot];			// so we don't kill in modal loop
	[theClientRecord free];
	[self sendSpotListToClients];
	return self;
}

- (void) clientTerminated:(in id)sender
{
	int	i, n = [clientList count];
	id theClient;

	for (i = 0; i < n; i++)
	{
		id theClientRecord = [clientList objectAt:i];
		theClient = [theClientRecord client];

		if (theClient == sender)
		{
			[self nukeClient: theClientRecord];
			return;
		}
	}

	return;
}

- (oneway void) sendSpotListToClients 
{
	int	i, n = [clientList count];
	id theClient;

	for (i = 0; i < n; i++)
	{
		theClient = [[clientList objectAt:i] client];

		if (theClient != self)
		{
			[theClient useSpotList:spotList];
		}
		else [mySpotView display];
	}

	return;
}

// the point is passed in view coordinates; it is interpreted in the 
// server's view's coordinate system.  It returns a spot object if
// possible, which creates a proxy to that object on the client side

- getSpotForPoint:(NXPoint) pnt spotLocation:(out NXPoint *)loc
{
	int	n = [spotList count];
	int i;
	
	for (i = n-1; i >= 0; i--)
	{
		id	aSpot = [spotList objectAt:i];
		NXPoint spnt = [aSpot location];
		if (pnt.x > spnt.x && pnt.x < spnt.x + 30 &&
			pnt.y > spnt.y && pnt.y < spnt.y + 30)
		{
			if ([aSpot doLock])
			{
				*loc = spnt;
				return aSpot;
			}
			return nil;
		}
	}
	return nil;
}

- (oneway void) spotDidChange
{
	[server sendSpotListToClients];
	return;
}

//////////////////////////////////////////////////////
//		Client code
//////////////////////////////////////////////////////

// return the clients own copy of the spotList
- spotList
{
	return spotList;
}

// don't ever send this message to the server!
- (oneway void) useSpotList: (bycopy in id) newSpotList
{
	[spotList freeObjects];
	[spotList free];
	spotList = newSpotList;
	[mySpotView display];
	return;
}

// the dying server calls this to nuke the clients
- (oneway void) serverTerminated
{
	NXRunAlertPanel (NULL, "Server terminated.  Helpless client now "
			"terminating as well.", "Oot!", NULL, NULL);
	[NXApp terminate:self];
}

@end
